package cn.iocoder.yudao.module.wms.service.inventory;

import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.ServerException;
import cn.iocoder.yudao.module.wms.dal.dataobject.stock.StockDO;
import cn.iocoder.yudao.module.wms.dal.dataobject.warehouse.WarehouseDO;
import cn.iocoder.yudao.module.wms.dal.redis.RedisKeyConstants;
import cn.iocoder.yudao.module.wms.service.stock.StockService;
import cn.iocoder.yudao.module.wms.service.warehouse.WarehouseService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.List;
import java.util.Set;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.INVENTORY_HANDLER_FAILD;
import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.WAREHOUSE_OPEN_POSTION;

/**
 * @Author: Ljx
 * @createTime: 2024年05月12日 11:40:58
 * @version: v1.0.0
 * @Description: 库存处理抽象模板
 */
public abstract class InventoryAbstract {
    @Resource
    private StockService stockService;
    @Resource
    private WarehouseService warehouseService;
    @Resource
    private RedissonClient redissonClient;

    /**
     * 增加库存
     * @param productId 产品
     * @param warehouseId 仓库
     * @param positionId 库位
     * @param qty 库存
     * @param unitAmt 单位成本
     * @param costAmt 合计成本
     * @param batchNo 批次号
     * @param serialNo 序列号
     * @param docId 单据
     * @param docCode 单号
     * @param sequence 序号
     */
    @Transactional(rollbackFor = Exception.class)
    public void increaseInventory(Long productId, Long warehouseId, Long positionId, BigDecimal qty, BigDecimal unitAmt, BigDecimal costAmt, String batchNo, List<String> serialNo, Long docId, String docCode, Integer sequence) {
        WarehouseDO warehouse = validateWarehouse(warehouseId, positionId);
        RLock lock = redissonClient.getLock(RedisKeyConstants.WMS_PRODUCT_LOCK + productId);
        try {
            lock.lock();
            // 增加库存汇总
            stockService.handleStock(productId, warehouse, qty);
            // 增加库存明细
            this.handleIncreaseStockDetail(productId, warehouseId, positionId, qty, unitAmt, costAmt, batchNo, serialNo, docId, docCode, sequence);
        }catch (ServerException se) {
            throw exception(se.getCode(), se.getMessage());
        }catch (Exception e) {
           throw exception(INVENTORY_HANDLER_FAILD, e.getMessage());
        }finally {
            lock.unlock();
        }
    }

    /**
     * 减少库存
     * @param productId
     * @param warehouseId
     * @param positionId
     * @param qty
     * @param batchNo
     * @param serialNo
     * @param docId
     * @param docCode
     */
    @Transactional(rollbackFor = Exception.class)
    public void decreaseInventory(Long productId, Long warehouseId, Long positionId, BigDecimal qty, String batchNo, List<String> serialNo, Long docId, String docCode){
        WarehouseDO warehouse = validateWarehouse(warehouseId, positionId);
        RLock lock = redissonClient.getLock(RedisKeyConstants.WMS_PRODUCT_LOCK + productId);
        try {
            lock.lock();
            // 减少库存汇总
            stockService.handleStock(productId, warehouse, qty.negate());
            // 减少库存明细
            this.handleDecreaseStockDetail(productId, warehouseId, positionId, qty.negate(), batchNo, serialNo, docId, docCode);
        }catch (ServerException se) {
            throw exception(se.getCode(), se.getMessage());
        }catch (Exception e) {
            throw exception(INVENTORY_HANDLER_FAILD, e.getMessage());
        }finally {
            lock.unlock();
        }
    }

    /**
     * 撤回增加库存
     * @param productId
     * @param warehouseId
     * @param positionId
     * @param qty
     * @param batchNo
     * @param serialNo
     * @param sequence
     */
    @Transactional(rollbackFor = Exception.class)
    public void revertIncreaseInventory(Long productId, Long warehouseId, Long positionId, BigDecimal qty, String batchNo, List<String> serialNo, Integer sequence){
        WarehouseDO warehouse = validateWarehouse(warehouseId, positionId);
        RLock lock = redissonClient.getLock(RedisKeyConstants.WMS_PRODUCT_LOCK + productId);
        try {
            lock.lock();
            // 减少库存汇总
            stockService.handleStock(productId, warehouse, qty.negate());
            // 还原明细
            this.handleRevertIncreaseStockDetail(productId, warehouseId, positionId, qty.negate(), batchNo, serialNo, sequence);
        }catch (ServerException se) {
            throw exception(se.getCode(), se.getMessage());
        }catch (Exception e) {
            throw exception(INVENTORY_HANDLER_FAILD, e.getMessage());
        }finally {
            lock.unlock();
        }

    }

    /**
     * 撤回减少库存
     * @param productId
     * @param warehouseId
     * @param positionId
     * @param qty
     * @param unitAmt 单位成本
     * @param costAmt 合计成本
     * @param batchNo
     * @param serialNo
     * @param docId
     * @param docCode
     */
    @Transactional(rollbackFor = Exception.class)
    public void revertDecreaseInventory(Long productId, Long warehouseId, Long positionId, BigDecimal qty, BigDecimal unitAmt, BigDecimal costAmt, String batchNo, List<String> serialNo, Long docId, String docCode){
        WarehouseDO warehouse = validateWarehouse(warehouseId, positionId);
        RLock lock = redissonClient.getLock(RedisKeyConstants.WMS_PRODUCT_LOCK + productId);
        try {
            lock.lock();
            // 增加库存汇总
            stockService.handleStock(productId, warehouse, qty);
            // 还原明细
            this.handleRevertDecreaseStockDetail(productId, warehouseId, positionId, qty, unitAmt, costAmt, batchNo, serialNo, docId, docCode);
        }catch (ServerException se) {
            throw exception(se.getCode(), se.getMessage());
        }catch (Exception e) {
            throw exception(INVENTORY_HANDLER_FAILD, e.getMessage());
        }finally {
            lock.unlock();
        }
    }

    /**
     * 增加库存明细
     * @param productId
     * @param warehouseId
     * @param positionId
     * @param qty
     * @param unitAmt
     * @param costAmt
     * @param batchNo
     * @param serialNo
     * @param docId
     * @param docCode
     * @param sequence
     */
    protected abstract void handleIncreaseStockDetail(Long productId, Long warehouseId, Long positionId, BigDecimal qty, BigDecimal unitAmt, BigDecimal costAmt, String batchNo, List<String> serialNo, Long docId, String docCode, Integer sequence);

    /**
     * 减少库存明细
     * @param productId
     * @param warehouseId
     * @param positionId
     * @param qty
     * @param batchNo
     * @param serialNo
     * @param docId
     * @param docCode
     */
    protected abstract void handleDecreaseStockDetail(Long productId, Long warehouseId, Long positionId, BigDecimal qty, String batchNo, List<String> serialNo, Long docId, String docCode);

    /**
     * 撤回增加的库存明细
     * @param productId
     * @param warehouseId
     * @param positionId
     * @param qty
     * @param batchNo
     * @param serialNo
     * @param sequence
     */
    protected abstract void handleRevertIncreaseStockDetail(Long productId, Long warehouseId, Long positionId, BigDecimal qty, String batchNo, List<String> serialNo, Integer sequence);

    /**
     * 撤回减少的库存明细
     * @param productId
     * @param warehouseId
     * @param positionId
     * @param qty
     * @param unitAmt
     * @param costAmt
     * @param batchNo
     * @param serialNo
     * @param docId
     * @param docCode
     */
    protected abstract void handleRevertDecreaseStockDetail(Long productId, Long warehouseId, Long positionId, BigDecimal qty, BigDecimal unitAmt, BigDecimal costAmt, String batchNo, List<String> serialNo, Long docId, String docCode);

    /**
     * 校验仓库信息
     * @param warehouseId
     * @param positionId
     * @return
     */
    private WarehouseDO validateWarehouse(Long warehouseId, Long positionId) {
        WarehouseDO warehouse = warehouseService.getWarehouse(warehouseId);
        if (CommonStatusEnum.ENABLE.getStatus().equals(warehouse.getOpenPosition()) && ObjectUtil.isEmpty(positionId)) {
            throw exception(WAREHOUSE_OPEN_POSTION);
        }
        if (ObjectUtil.isNotEmpty(positionId)) {
            warehouseService.getPosition(positionId);
        }
        return warehouse;
    }
}
